Fire cases in London 2019-2022 ¶
Group number 21 ¶
Names of students:¶
Shahar Lavi
Yeheli Rot
From:
Link to Kaggle: https://www.kaggle.com/datasets/timmofeyy/-fire-cases-in-uk-within-last-3-years/data
Kaggle sources: https://data.london.gov.uk/dataset/london-fire-brigade-incident-records.
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import numpy as np
import seaborn as sns
from IPython.display import display, HTML
import requests
import json
Getting to know the data: ¶
- הבנת העמודות והנתונים
- סינון נתונים לא רלוונטים ובדיקת עמודות ריקות
- הצגת הקורולציה
url1 = 'https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/fire_cases_part_1.csv'
url2 = 'https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/fire_cases_part_2.csv'
url3='https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/fire_cases_part_3.csv'
url4='https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/fire_cases_part_4.csv'
url5='https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/fire_cases_part_5.csv'
url6='https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/fire_cases_part_6.csv'
fire_df1 = pd.read_csv(url1)
fire_df2 = pd.read_csv(url2)
fire_df3 = pd.read_csv(url3)
fire_df4 = pd.read_csv(url4)
fire_df5 = pd.read_csv(url5)
fire_df6 = pd.read_csv(url6)
fire_df = pd.concat([fire_df1 ,fire_df2,fire_df3,fire_df4,fire_df5,fire_df6]).reset_index()
fire_df
fire_df.shape
fire_df.columns
מחקנו מהדאטה עמודות לא רלוונטיות לשאלת המחקר ועמודות בעלות משמעות כפולה
columns_to_drop = [
"UPRN", "USRN",
"AddressQualifier",
"ProperCase",
"Easting_m", "Northing_m",
"Easting_rounded", "Northing_rounded",
"IncGeo_WardCode", "IncGeo_WardName", "IncGeo_WardNameNew",
]
df_cleaned = fire_df.drop(columns=columns_to_drop)
df_cleaned.to_csv("fire_cases_cleaned.csv", index=False)
החלטנו לעשות קורלציה לכל הדאטה, על מנת לראות קשרים בין הנתונים ולבחור במה להתעמק ולהתמקד:
correlation_matrix = df_cleaned.corr(numeric_only=True)
plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=True, fmt=".2f", cmap="coolwarm", square=True, cbar_kws={"shrink": .8})
plt.title("Correlation Matrix Between Numerical Variables")
plt.tight_layout()
plt.show()
First path :
היקף הקריאות ביחס למאפיינים תפעוליים וחומרת השריפות
- האם קיים קשר בין מספר השיחות למוקד הכבאות בלונדון לבין כמות הכבאיות, משך הפעולה ועלות הכיבוי?
- כיצד משפיעה רמת חומרת השריפה על כמות השיחות הנכנסות?
בהמשך לניתוח הקורלציה שערכנו, בחנו שלושה קשרים עיקריים:
בין מספר השיחות לבין מספר הכבאיות שנשלחו לאירוע, העלות המשוערת של הטיפול באירוע, ושעות הפעולה של הכבאיות. מטרת בדיקה זו היא להבין האם אירועים שמעוררים יותר פניות ציבוריות אכן מאופיינים במענה מבצעי משמעותי יותר.
plt.figure(figsize=(14, 6))
color1 = "#f4a261"
color2 = "#e76f51"
color3 = "#f7d794"
plt.subplot(1, 3, 1)
sns.regplot(data=df_cleaned, x='NumCalls', y='PumpCount', scatter_kws={"alpha": 0.4, "color": color1}, line_kws={"color": color1})
plt.title('Number of Calls vs. Number of Pumps Dispatched')
plt.xlabel('Number of Calls')
plt.ylabel('Pump Count')
plt.subplot(1, 3, 2)
sns.regplot(data=df_cleaned, x='NumCalls', y='Notional Cost (£)', scatter_kws={"alpha": 0.4, "color": color2}, line_kws={"color": color2})
plt.title('Number of Calls vs. Estimated Response Cost')
plt.xlabel('Number of Calls')
plt.ylabel('Estimated Cost (£)')
plt.subplot(1, 3, 3)
sns.regplot(data=df_cleaned, x='NumCalls', y='PumpHoursRoundUp', scatter_kws={"alpha": 0.4, "color": color3}, line_kws={"color": color3})
plt.title('Number of Calls vs. Pump Work Hours')
plt.xlabel('Number of Calls')
plt.ylabel('Pump Work Hours')
plt.tight_layout()
plt.show()
בגרף זיהינו שתי נקודות חריגות: האחת עם כ-20 שיחות, שבה נרשמה הפעלת משאבים וזמן טיפול חריגים בגובהם, והשנייה עם כ-175 שיחות, שבה גם הופעלו משאבים רבים אך בעוצמה מעט פחותה. מטרתנו הייתה לחקור חריגויות אלו כדי להבין מה הוביל להפעלת משאבים יוצאת דופן ביחס למספר השיחות שהתקבלו.
unusual_df = fire_df[(fire_df["NumCalls"] > 170) | (fire_df["PumpHoursRoundUp"] > 1000)]
selected_columns = ["PropertyCategory", "PropertyType", "StopCodeDescription", "AddressQualifier",
"IncGeo_BoroughName", "NumCalls", "Notional Cost (£)", "PumpHoursRoundUp", "PumpCount"]
unusual_df[selected_columns]
סביר להניח שההבדל בכמות השיחות נובע לא מהחומרה האובייקטיבית של האירוע, אלא מהחשיפה הציבורית למוקד השריפה – כאשר אירועים באזורים עירוניים נוטים לקבל יותר שיחות גם אם בפועל הם פחות חמורים.
סיננו את הנתונים החריגים שבהם מספר השיחות עולה על 75, והורדנו את הנתונים החריגים בשלושת הקטגוריות הנוספות, וכעת הצגנו מחדש את הגרפים עם חלוקה לקבוצות בטווחי חמש שיחות, כדי להמחיש טוב יותר את ההבדלים בין המשתנים ביחס למספר השיחות.
fire_df.dropna(subset=["NumCalls", "PumpCount", "Notional Cost (£)", "PumpHoursRoundUp"], inplace=True)
df = fire_df[
(fire_df["NumCalls"] > 0) & (fire_df["NumCalls"] <= 75) &
(fire_df["Notional Cost (£)"] < 250000) &
(fire_df["PumpHoursRoundUp"] < 700) &
(fire_df["PumpCount"] < 150)
].copy()
df.loc[:, "CallsGroup"] = pd.cut(
df["NumCalls"],
bins=[1, 15, 30, 45, 60, 75],
labels=["1-15 calls", "16-30 calls", "31-45 calls", "46-60 calls", "61-75 calls"]
)
group_means = df.groupby("CallsGroup", observed=False).agg({
"PumpCount": "mean",
"Notional Cost (£)": "mean",
"PumpHoursRoundUp": "mean"
}).reset_index()
fig, axes = plt.subplots(1, 3, figsize=(22, 5))
sns.barplot(data=group_means, x="CallsGroup", y="PumpCount", hue="CallsGroup", palette="Reds", legend=False, ax=axes[0])
axes[0].set_title("Average Number of Pumps Dispatched by Call Group")
axes[0].set_xlabel("Call Group")
axes[0].set_ylabel("Average Pumps Dispatched")
axes[0].grid(True)
sns.barplot(data=group_means, x="CallsGroup", y="Notional Cost (£)", hue="CallsGroup", palette="Oranges", legend=False, ax=axes[1])
axes[1].set_title("Average Incident Cost by Call Group")
axes[1].set_xlabel("Call Group")
axes[1].set_ylabel("Average Incident Cost (£)")
axes[1].grid(True)
sns.barplot(data=group_means, x="CallsGroup", y="PumpHoursRoundUp", hue="CallsGroup", palette="YlOrBr", legend=False, ax=axes[2])
axes[2].set_title("Average Pump Hours by Call Group")
axes[2].set_xlabel("Call Group")
axes[2].set_ylabel("Average Pump Hours")
axes[2].grid(True)
plt.tight_layout()
plt.show()
בהתבסס על הנתונים, זוהה קשר חיובי מובהק בין מספר השיחות שהתקבלו בנוגע לאירוע לבין עוצמת התגובה המבצעית, המתבטאת במספר המשאבות שהופעלו, עלות הטיפול וזמן הפעולה. ממצא זה מרמז כי מספר השיחות עשוי לשמש כמדד המשקף את חומרת האירוע.
רצינו לבדוק את הקשר בין מספר השיחות שהתקבלו לאירוע לבין רמת החומרה של האירוע, על ידי מיפוי סוגי האירועים לרמות חומרה מספריות והצגת מגמת השינוי בעזרת קו מגמה חלק (lowess), כדי להבין האם אירועים עם יותר שיחות נוטים להיות חמורים יותר.
fire_df['StopCodeDescription'].unique()
df = df.copy()
severity_map = {
'AFA': 1,
'False alarm - Good intent': 1,
'False alarm - Malicious': 2,
'Late Call': 2,
'Use of Special Operations Room': 2,
'Flood call attended - Batch mobilised': 3,
'Special Service': 3,
'Chimney Fire': 3,
'Secondary Fire': 4,
'Primary Fire': 5
}
df.loc[:, 'SeverityLevel'] = df['StopCodeDescription'].map(severity_map)
df.loc[:, 'CallsBin'] = pd.cut(df['NumCalls'], bins=[0, 15, 30, 45, 60, 75])
bin_summary = df.groupby('CallsBin', observed=True).agg({
'SeverityLevel': 'mean',
'PumpHoursRoundUp': 'mean',
'PumpCount': 'mean',
'Notional Cost (£)': 'mean',
'StopCodeDescription': 'count'
}).reset_index().rename(columns={'StopCodeDescription': 'EventCount'})
bin_summary['CallsBin'] = bin_summary['CallsBin'].astype(str)
fig, axes = plt.subplots(1, 2, figsize=(16, 6), sharex=False)
ax1 = axes[0]
ax1b = ax1.twinx()
color1 = 'darkred'
ax1.plot(bin_summary['CallsBin'], bin_summary['SeverityLevel'], marker='o', color=color1, label='Severity Level', linewidth=2)
ax1.set_ylabel('Average Severity Level', color=color1)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.set_ylim(1, 5.2)
for i, (sev, count) in enumerate(zip(bin_summary['SeverityLevel'], bin_summary['EventCount'])):
ax1.text(i, sev + 0.15, f'Count: {count}', ha='center', color=color1, fontsize=9)
color2 = 'darkblue'
color3 = 'green'
ax1b.plot(bin_summary['CallsBin'], bin_summary['PumpHoursRoundUp'], marker='s', color=color2, label='Pump Hours', linewidth=2)
ax1b.plot(bin_summary['CallsBin'], bin_summary['PumpCount'], marker='^', color=color3, label='Pump Count', linewidth=2)
ax1b.set_ylabel('Avg Pump Hours / Pump Count', color='black')
ax1b.tick_params(axis='y', labelcolor='black')
ax1.set_title('Severity vs. Pump Hours & Count')
ax1.set_xlabel('Number of Calls (Binned)')
ax1.grid(True, linestyle='--', alpha=0.5)
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax1b.get_legend_handles_labels()
ax1b.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
ax2 = axes[1]
ax2b = ax2.twinx()
ax2.plot(bin_summary['CallsBin'], bin_summary['SeverityLevel'], marker='o', color=color1, label='Severity Level', linewidth=2)
ax2.set_ylabel('Average Severity Level', color=color1)
ax2.tick_params(axis='y', labelcolor=color1)
ax2.set_ylim(1, 5.2)
color4 = 'darkorange'
ax2b.plot(bin_summary['CallsBin'], bin_summary['Notional Cost (£)'], marker='D', color=color4, label='Notional Cost (£)', linewidth=2)
ax2b.set_ylabel('Average Notional Cost (£)', color=color4)
ax2b.tick_params(axis='y', labelcolor=color4)
for i, (sev, count) in enumerate(zip(bin_summary['SeverityLevel'], bin_summary['EventCount'])):
ax2.text(i, sev + 0.15, f'Count: {count}', ha='center', color=color1, fontsize=9)
ax2.set_title('Severity vs. Notional Cost')
ax2.set_xlabel('Number of Calls (Binned)')
ax2.grid(True, linestyle='--', alpha=0.5)
lines1, labels1 = ax2.get_legend_handles_labels()
lines2, labels2 = ax2b.get_legend_handles_labels()
ax2b.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
fig.suptitle('Fire Event Severity and Resources by Number of Calls', fontsize=16)
plt.tight_layout()
plt.show()
ככל שמספר השיחות לאירוע עולה, מושקעים יותר משאבים והעלות המשוערת גבוהה יותר. עם זאת, רמת החומרה לא תמיד עולה בהתאם – ייתכן שהירידה בחומרה בקבוצות עם מספר שיחות גבוה נובעת ממיעוט מקרים בקבוצות אלה, ולכן המגמה אינה בהכרח מייצגת.
מסקנות עיקריות:
-
מהשוואת הגרפים עולה כי ככל שמספר השיחות לאירוע גבוה יותר, ניכרת עלייה בשימוש במשאבים – הן במספר המשאבות והן בשעות העבודה ובעלות המשוערת. עם זאת, רמת החומרה הממוצעת אינה ממשיכה לעלות באותו קצב ואף יורדת בקבוצת השיחות הגבוהה ביותר. ייתכן שהסבר לכך טמון בכך שבקבוצה זו יש מעט מאוד מקרים, ולכן הממוצעים בה פחות מייצגים. בנוסף, ייתכן שמקרים אלו התרחשו במיקומים ציבוריים – מה שגרם לריבוי שיחות, אך לא בהכרח מעיד על חומרת שריפה גבוהה, כפי שניתן היה לראות גם בשני האירועים החריגים שניתחנו.
ניסינו לבדוק ולדרג את שכונות לונדון לפי כמות אירועי השריפות, מהשכונה עם מספר השריפות הגבוה ביותר ועד לזו עם המספר הנמוך ביותר.
borough_counts = df_cleaned['IncGeo_BoroughName'].value_counts().reset_index()
borough_counts.columns = ['Boroughs', 'Count']
plt.figure(figsize=(12, 8))
palette = sns.light_palette("#c75c4c", n_colors=len(borough_counts), reverse=True)
sns.barplot(data=borough_counts, x='Count', y='Boroughs', hue='Boroughs', palette=palette, dodge=False, legend=False)
plt.title('Number of Incidents by Borough (IncGeo_BoroughName)', fontsize=14)
plt.xlabel('Number of Incidents', fontsize=12)
plt.ylabel('Boroughs', fontsize=12)
plt.tight_layout()
plt.show()
הצגנו את הפיזור הגאוגרפי של אירועי השריפות ברחבי לונדון באמצעות מפת חום.
borough_counts['Boroughs'] = borough_counts['Boroughs'].str.strip().str.upper()
url = "https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/london_boroughs.geojson"
response = requests.get(url)
geojson = response.json()
for feature in geojson['features']:
feature['properties']['name'] = feature['properties']['name'].strip().upper()
fig = px.choropleth_mapbox(
borough_counts,
geojson=geojson,
locations='Boroughs',
featureidkey='properties.name',
color='Count',
color_continuous_scale="YlOrRd",
mapbox_style="carto-positron",
zoom=9,
center={"lat": 51.5074, "lon": -0.1278},
opacity=0.6,
labels={'Count': 'מספר אירועים'}
)
fig.update_layout(margin={"r":0,"t":30,"l":0,"b":0}, title_text='מספר אירועים לפי שכונות בלונדון')
fig.show()
מהניתוח של שני הגרפים הראשונים עולה כי מספר השריפות בלונדון מרוכז בעיקר באזור המרכזי של העיר. הערים המרכזיות, כגון Westminster ו-Camden, מציגות את שיעור השריפות הגבוה ביותר, בעוד שהאזורים המרוחקים יותר מהמרכז מאופיינים בשיעור שריפות נמוך יותר מסקנה זו מצביעה על כך שהסיכון לשריפות גבוה יותר באזורים בעלי צפיפות עירונית גבוהה.
בדקנו האם יש קשר בין גודל האוכלוסייה לבין מספר השריפות בכל שכונה. לשם כך, שילבנו קובץ חיצוני שמכיל נתונים על כמות האוכלוסייה בכל אחת משכונות לונדון, והשווינו אותם למספר אירועי השריפות באותן שכונות.
url_population = "https://raw.githubusercontent.com/YeheliRot/Fiers-cases-in-London/refs/heads/main/Population.csv"
pop_df = pd.read_csv(url_population)
fire_counts = df_cleaned['IncGeo_BoroughName'].value_counts().reset_index()
fire_counts.columns = ['Boroughs', 'FireCount']
fire_counts['Boroughs'] = fire_counts['Boroughs'].str.upper()
pop_df['Boroughs'] = pop_df['Boroughs'].str.upper()
merged_df = pd.merge(pop_df, fire_counts, on='Boroughs', how='left')
merged_df['FireCount'] = merged_df['FireCount'].fillna(0)
corr = merged_df['Population'].corr(merged_df['FireCount'])
print(f"Pearson correlation between Population and FireCount: {corr:.2f}")
sns.lmplot(data=merged_df, x='Population', y='FireCount', height=6, aspect=1.2)
plt.title(f"Correlation between Population and FireCount: {corr:.2f}")
plt.show()
מהגרף עולה כי קיימת קורלציה חיובית מתונה (0.37) בין כמות האוכלוסייה בכל שכונה לבין מספר השריפות שהתרחשו בה. ממצא זה מצביע על כך שככל שכמות האוכלוסייה בשכונה גבוהה יותר, כך יש נטייה לעלייה במספר אירועי השריפות. עם זאת, מאחר והקורלציה אינה גבוהה, ניתן להסיק כי אמנם צפיפות אוכלוסייה משפיעה במידה מסוימת על שכיחות השריפות, אך קיימים גם גורמים נוספים התורמים לתופעה זו.
table = merged_df[['Boroughs', 'Population', 'FireCount']].sort_values(by='FireCount', ascending=False)
display(table)
ניתחנו את סוגי הרכוש שנפגעו מהשריפות, במטרה לזהות אילו סוגי נכסים (מגורים, שטחים פתוחים, רכבים וכדומה) היו החשופים ביותר לפגיעה, ולהבין האם מרבית השריפות התרחשו באזורים עירוניים או בסביבות טבעיות.
plt.figure(figsize=(12, 6))
sns.countplot(
data=df_cleaned,
x='PropertyCategory',
hue='PropertyCategory',
order=df_cleaned['PropertyCategory'].value_counts().index,
palette='rocket_r',
legend=False
)
plt.title('Number of Fires by Property Category', fontsize=16)
plt.xlabel('Property Category', fontsize=14)
plt.ylabel('Number of Fires', fontsize=14)
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.show()
מסקנה: ¶
מניתוח הנתונים עולה כי אירועי השריפות בלונדון אינם מתפלגים באופן אקראי, אלא מתרכזים בעיקר באזורים המרכזיים של העיר, המאופיינים בצפיפות אוכלוסייה גבוהה. הקורלציה שנמצאה בין כמות האוכלוסייה למספר השריפות מצביעה על קשר מסוים, אך לא בלעדי, בין צפיפות לבין סיכון לשריפות. בנוסף, ניתוח סוגי הרכוש שנפגעו מצביע על כך שהנזקים מתרכזים בעיקר בנכסים עירוניים, מה שמחזק את המסקנה כי מרבית השריפות מתרחשות באזורים מיושבים.
רצינו לבדוק באילו שכונות בלונדון העלות הכוללת של הכיבוי היא הגבוהה ביותר, והאם קיימת התאמה בין מספר מקרי השריפה לבין העלות הכוללת בשכונות השונות.
incident_counts = df['IncGeo_BoroughName'].value_counts().reset_index()
incident_counts.columns = ['Borough', 'Count']
costs_by_borough = df_cleaned.groupby('IncGeo_BoroughName')['Notional Cost (£)'].sum().reset_index()
costs_by_borough.columns = ['Borough', 'TotalCost']
merged = pd.merge(incident_counts, costs_by_borough, on='Borough')
merged = merged.sort_values(by='Count', ascending=False)
palette = sns.light_palette("#c75c4c", n_colors=len(merged), reverse=True)
borough_palette = dict(zip(merged['Borough'], palette))
fig, axes = plt.subplots(1, 2, figsize=(18, 10), sharey=True)
sns.barplot(
ax=axes[0], data=merged, x='Count', y='Borough',
hue='Borough', palette=borough_palette, legend=False
)
axes[0].set_title('Number of Incidents by Borough', fontsize=14)
axes[0].set_xlabel('Number of Incidents', fontsize=12)
axes[0].set_ylabel('Borough', fontsize=12)
merged_sorted_by_cost = merged.sort_values(by='TotalCost', ascending=False)
sns.barplot(
ax=axes[1], data=merged_sorted_by_cost, x='TotalCost', y='Borough',
hue='Borough',
palette=[borough_palette[b] for b in merged_sorted_by_cost['Borough']],
legend=False
)
axes[1].set_title('Total Notional Cost (£) by Borough', fontsize=14)
axes[1].set_xlabel('Total Notional Cost (£)', fontsize=12)
axes[1].set_ylabel('')
plt.tight_layout()
plt.show()
מהגרף ניתן לראות שלרוב יש התאמה בין מספר מקרי השריפה לעלות הכוללת, אך ישנם שכונות שבהם העלות גבוהה למרות מספר נמוך יותר של אירועים.
הסרנו את שנת 2022 מהניתוח כי הנתונים עבורה חלקיים (רק עד פברואר), והשוואה לשנים שלמות הייתה יוצרת הטיה בתוצאות.
filtered_df = fire_df[fire_df["CalYear"] != 2022]
הגרף מציג את העלות הכוללת של קריאות לשירותי הכבאות בשנים 2019–2021.
הניתוח בוצע במטרה לבחון את השינויים בעלות הכוללת לאורך השנים.
בגרף זה נבחנה העלות הכוללת של קריאות לשירותי הכבאות בהתאם לסוג האירוע והעלות השנתית הכוללת.
מטרת הניתוח הייתה לזהות אילו סוגי אירועים גוררים את העלות הגבוהה ביותר עבור שירותי הכבאות והאם יש הבדלים בין השנים.
df_cleaned = filtered_df[["CalYear", "IncidentGroup", "Notional Cost (£)"]].dropna()
incident_costs = df_cleaned.groupby(["CalYear", "IncidentGroup"])["Notional Cost (£)"].sum().unstack(fill_value=0)
yearly_costs = df_cleaned.groupby("CalYear")["Notional Cost (£)"].sum()
years = incident_costs.index.astype(str)
categories = incident_costs.columns
x = np.arange(len(years))
width = 0.25
colors = sns.light_palette("#c75c4c", n_colors=len(categories), reverse=True)
plt.figure(figsize=(10, 6))
for i, cat in enumerate(categories):
plt.bar(x + i*width, incident_costs[cat], width=width, label=cat, color=colors[i], edgecolor="black")
for i, year in enumerate(years):
total_cost = yearly_costs[int(year)]
center_x = x[i] + width
plt.text(center_x, incident_costs.iloc[i].max() + 50000, f"£{total_cost:,.0f}", ha='center', fontsize=9, fontweight='bold', color='#5a2e2e')
plt.xticks(x + width, years)
plt.title("Incident Types by Year with Total Cost (excluding 2022)", fontsize=14)
plt.xlabel("Year")
plt.ylabel("Total Cost (£)")
plt.legend(title="Incident Type")
plt.grid(axis='y', linestyle='--', alpha=0.4)
plt.tight_layout()
plt.show()
בגרף זה בדקנו את היקף הקריאות מכל סוג, בעקבות הממצא שקריאות שווא הן היקרות ביותר.
המטרה הייתה להבין האם העלות הגבוהה נובעת מכמות גדולה של קריאות מסוג זה.
def classify_call(row):
if row['IncidentGroup'] == 'False Alarm':
return 'False Alarms'
elif row['IncidentGroup'] == 'Fire':
return 'Real Fire Calls'
else:
return 'Special Cases'
df.loc[:, 'CallType'] = df.apply(classify_call, axis=1)
call_counts = df['CallType'].value_counts()
plt.figure(figsize=(7, 7))
palette = sns.light_palette("#c75c4c", n_colors=3, reverse=True)
plt.pie(
call_counts,
labels=call_counts.index,
autopct='%1.1f%%',
startangle=140,
colors=palette
)
plt.title('Distribution of Fire Department Calls by Type')
plt.axis('equal')
plt.show()
נבדוק מה סוג האירוע בפועל שגורם למרבית קריאות השווא
false_alarms_df = df[df['CallType'] == 'False Alarms']
false_alarm_types = false_alarms_df['StopCodeDescription'].value_counts()
false_alarm_types
סוג האירוע עם הרוב המוחלט של קריאות שווא הוא AFA-אזעקה אוטומטית
calls_by_type_borough = df.groupby(['IncGeo_BoroughName', 'CallType']).size().unstack(fill_value=0)
calls_by_type_borough['Total Calls'] = calls_by_type_borough.sum(axis=1)
calls_by_type_borough['False Alarm Ratio'] = calls_by_type_borough['False Alarms'] / calls_by_type_borough['Total Calls']
calls_by_type_borough['Real Call Ratio'] = 1 - calls_by_type_borough['False Alarm Ratio']
cost_per_borough = df.groupby('IncGeo_BoroughName')['Notional Cost (£)'].sum()
data = pd.DataFrame({
'Cost': cost_per_borough,
'False Alarm Ratio': calls_by_type_borough['False Alarm Ratio'],
'Real Call Ratio': calls_by_type_borough['Real Call Ratio'],
}).fillna(0)
data = data.sort_values(by='Cost', ascending=False)
data['Cost False Alarms'] = data['Cost'] * data['False Alarm Ratio']
data['Cost Real Calls'] = data['Cost'] * data['Real Call Ratio']
fig, ax = plt.subplots(figsize=(14, 8))
bars_real = ax.bar(data.index, data['Cost Real Calls'], color='tomato', label='Real Calls (Fire + Special)')
bars_false = ax.bar(data.index, data['Cost False Alarms'], bottom=data['Cost Real Calls'], color='orange', label='False Alarms')
ax.set_ylabel('Cost (£)', fontsize=12)
ax.set_xlabel('Borough', fontsize=12)
ax.set_title('Total Cost per Borough divided by Call Type', fontsize=16)
ax.set_xticks(range(len(data.index)))
ax.set_xticklabels(data.index, rotation=45, ha='right')
ax.legend()
plt.tight_layout()
plt.show()
הגרף מציג את הפיצול בין עלויות של התרעות שווא לאירועים אמיתיים בכל שכונה, ומדגיש כי באזורים מסוימים התרעות השווא מהוות את רוב העלות הכוללת.
מסקנה: ¶
בהתבסס על הנתונים המוצגים בגרפים, עולה כי מרבית האירועים במחוזות לונדון הם התרעות שווא, אשר מהוות את הרכיב המרכזי בעלויות התפעוליות של המערכת לאורך השנים. נתוני סיבת האירוע מצביעים על כך ש-127,968 מתוך מקרי התרעות השווא נגרמו כתוצאה מאזעקות אוטומטיות (AFA), מה שמעיד על השפעה משמעותית של סוג זה על כלל המשאבים והעלויות.
מסקנה זו מקבלת חיזוק משמעותי גם מהגרף המציג את העלות הכוללת לפי שכונה, המחולקת לפי סוג קריאה: מהגרף עולה כי ברוב השכונות חלק ניכר מהעלויות נובע מהתרעות שווא – לעיתים אף יותר מהעלויות של אירועים אמיתיים (שריפות ושירותים מיוחדים). נתון זה מדגיש את
תרומתה המרכזית של תופעת התרעות השווא, ובפרט אזעקות אוטומטיות, לנטל הכלכלי על שירותי החירום העירוניים.
Summery: ¶
הניתוח מצביע על כך שככל שמספר השיחות לאירוע גבוה יותר, כך גם השימוש במשאבים עולה, אך כאשר כמות השיחות גבוהה רמת החומרה אינה בהכרח מתגברת בהתאם, ככל הנראה מאחר ומדובר בקבוצות קטנות. שריפות מרוכזות בעיקר באזורים עירוניים צפופי אוכלוסין, דבר שמצביע על קשר מסוים בין צפיפות לבין הסיכון לשריפות. בנוסף, התרעות שווא – ובעיקר אזעקות אוטומטיות (AFA) – מהוות מרכיב משמעותי בעלות הכוללת של האירועים, ומשפיעות באופן מהותי על העומס על מערך החירום.